package im.composer.audio.engine;

import jass.engine.BufferNotAvailableException;
import jass.engine.SinkIsFullException;

import java.util.*;
import java.util.logging.Logger;

import org.jaudiolibs.audioservers.AudioConfiguration;
import org.tritonus.share.sampled.FloatSampleBuffer;


/**
 * Input/output unit. Needs only implementation of computeBuffer(). Each
 * attached source can be labeled passive, which means that we will call
 * getBuffer() without a time stamp on it, so no computation is triggered. This
 * is needed only when using the ThreadMixer which would result in deadlocks on
 * closed loops. So you have to explicitly mark source connections passive to
 * eliminate loops. When using only 1 thread this is not needed.
 * 
 * @author Kees van den Doel (kvdoel@cs.ubc.ca)
 */

public abstract class InOut extends Out implements Sink {

	private Vector<Source> sourceContainer = new Vector<Source>();
	private Vector<Boolean> sourcePassivity = new Vector<Boolean>();
	protected transient List<Source> read_only_container = Collections.unmodifiableList(sourceContainer);

	/**
	 * add source to Sink.
	 * 
	 * @param s
	 *            Source to add.
	 * @return object representing Source in Sink (may be null).
	 */
	public Object addSource(Source s) throws SinkIsFullException {
		return addSource(s,false);
	}

	/**
	 * add source to Sink, can flag as passive (so will trigger no computation)
	 * 
	 * @param s
	 *            Source to add.
	 * @param passive
	 *            flag; true if passive, otherwise will be active (normal)
	 * @return object representing Source in Sink (may be null).
	 */
	public synchronized Object addSource(Source s, boolean p) throws SinkIsFullException {
		if(s==null){
			throw new IllegalArgumentException();
		}
		sourceContainer.addElement(s);
		sourcePassivity.addElement(new Boolean(p));
		s.setTime(getTime());
		if(s instanceof InOut){
			try {
				((InOut)s).configure(getContext());
			} catch (Exception e) {
			}
		}
		return null;
	}

	public synchronized void removeSource(Source s) {
		int i = sourceContainer.indexOf(s);
		if(i<0){
			return;
		}
		removeSource(i);
	}

    @Override
	public void removeSource(int i) {
		if(i<0){
			return;
		}
		sourceContainer.removeElementAt(i);
		sourcePassivity.removeElementAt(i);
	}

	public List<Source> getSources() {
        return read_only_container;
    }

	/**
	 * Array of buffers of the sources
	 */
	protected transient FloatSampleBuffer[] srcBuffers;

	public InOut(AudioConfiguration context) {
		super(context);
	}

	@Override
	public synchronized void configure(AudioConfiguration context) throws Exception {
		super.configure(context);
		srcBuffers = new FloatSampleBuffer[1];
		for(Source src :sourceContainer){
			src.configure(context);
		}
	}

	/**
	 * Call all the sources and cache their returned buffers.
	 */
	protected final void callSources() {
		Source[] src = read_only_container.toArray(new Source[0]);
		int n = src.length; // number of sources
		int n_buf = srcBuffers.length; // number of source buffers allocated

		if (n_buf < n) {
			srcBuffers = new FloatSampleBuffer[n];
		}
		try {
			for (int i = 0; i < n; i++) {
				if (sourcePassivity.elementAt(i).booleanValue()) {
					srcBuffers[i] = (src[i]).getBuffer();
				} else {
					srcBuffers[i] = (src[i]).getBuffer(getTime());
				}
			}
		} catch (BufferNotAvailableException e) {
			Logger.getAnonymousLogger().severe("InOut.callSources: " + this + " " + e);
		}
	}

	@Override
	protected void prepareBuffer() {
		callSources();
		super.prepareBuffer();
	}

	/**
	 * Reset time of self and all inputs
	 * 
	 * @param t
	 *            time to reset to. Patch must be in a state s.t. none of the
	 *            current times == t
	 */
	public synchronized void resetTime(long t) {
		setTime(t);
		resetTimeSources(t);
	}

	/**
	 * Call all the sources and reset time
	 */
	private final void resetTimeSources(long t) {
		for(Source src:sourceContainer){
			if (src instanceof Out) {
				if (((Out) src).getTime() != t) {
					((Out) src).resetTime(t);
				}
			}
		}
	}

}
